Split long Telegram responses into multiple messages - #71
Conversation
Telegram rejects messages over 4096 characters, causing agent replies that exceed the limit to fail to send. Split responses into chunks that respect line boundaries (falling back to a hard split for oversized lines) before sending, so long replies are delivered across multiple messages instead of erroring out. Fixes ClawRunr#62
| for (String line : message.split("\n", -1)) { | ||
| while (line.length() > maxLength) { | ||
| flush(chunks, current); | ||
| chunks.add(line.substring(0, maxLength)); |
There was a problem hiding this comment.
This can split a surrogate pair (emoji) → invalid string, Telegram rejects it. Check Character.isHighSurrogate(line.charAt(maxLength - 1)) and back off by one.
There was a problem hiding this comment.
Fixed: findSafeSplitIndex now backs off one code unit when the cut index lands right after a high surrogate (Character.isHighSurrogate), so an emoji's surrogate pair is never split across chunks. Added test doesNotSplitEmojiSurrogatePairAcrossChunks covering this.
| } | ||
|
|
||
| public void sendMessage(long chatId, Integer messageThreadId, String message) { | ||
| for (String chunk : splitMessage(message, MAX_MESSAGE_LENGTH)) { |
There was a problem hiding this comment.
Splitting happens on raw markdown, but chunks are HTML-converted after escaping (< → <) and tags (** → ) make the rendered text longer, so a 4000-char chunk can still exceed 4096 and get rejected. Either measure rendered length when chunking
There was a problem hiding this comment.
Fixed: chunk boundaries are now chosen by binary-searching the HTML-rendered length (renderedLength, which runs the markdown-to-HTML conversion) rather than the raw markdown length, so escaping/tag expansion is accounted for before a chunk is emitted. Added test splitChunksRespectRenderedHtmlLengthNotRawMarkdownLength covering this.
… chunking Split points are now found via binary search over the HTML-rendered length instead of the raw markdown length, and a cut is backed off by one code unit if it would break a UTF-16 surrogate pair (emoji). Fixes both issues raised in review: truncation could exceed 4096 once markdown was rendered to HTML, and truncation could split an emoji into invalid halves.
Summary
sendMessagecalls over 4096 characters, so agent replies longer than that limit were failing to send.TelegramChannel.sendMessagenow splits the response into chunks (respecting line boundaries, with a hard-split fallback for oversized single lines) and sends each chunk as a separate Telegram message.Fixes #62
Test plan
./gradlew :plugins:telegram:testpasses.